ε Curso de assembler (V) ε ---------------------- En este nuevo número veremos todo lo concerniente a las instrucciones δlógicasπ, profundizaremos en los registros y veremos que son los modos de direccionamiento. Con lo aprendido en los anteriores números (bifurcaciones y instrucciones aritméticas) más lo que veremos en este, deberiais ser ya capaces de programar en ensamblador cositas sencillas usando interrupciones y manipulando datos. Si todavia no sois capaces de hacerlo, seria muy recomendable invertir más tiempo practicandolo ya que esa es la única forma de aprender. Antes de nada, cuando hablamos de las instrucciones de bifurcación se nos quedó en el tintero la instrucción δLOOPπ. A esta instrucción se le pasa un único parámetro que es una etiqueta donde saltará. Lo que hace es decrementar φCXπ (CX=CX-1) y si CX no es 0 saltará a la etiqueta que le pasemos, sino continuará por donde estaba. Un ejemplo: Γ ··· Γ XOR DX, DX Ω ; DX = 0 (forma rápida de poner a 0). Γ MOV CX, 100 Ω ; Repetir 100 veces. Γ Bucle: Γ INC DX Ω ; DX = DX + 1 Γ LOOP Bucle Ω ; Si CX no es 0, repetir el bucle otra vez. Γ ··· Ω ; En este punto, DX valdrà 100. φ Instrucciones lógicas Seguro que recordais las tablas de verdad que veiamos en los primeros números de la Phy, ¿verdad? Pues las operaciones lógicas lo que hacen es trabajar sobre estas tablas de verdad con registros o posiciones de memoria de un tamaño en bits determinado. Por ejemplo, la tabla de la operación OR era así: Γ 0011 Γ 0101 Γ ---- Γ 0111 Si por ejemplo tenemos en AL el valor binario <01001110> (recuerda que AL es de 8 bits) y en BL tenemos el valor <11100110> y le aplicamos un δORπ de la forma "OR AL,BL", el resultado será: AL=<11101110>. Γ AL -> 01001110 Γ BL -> 11100110 Γ -------------- ΩOR AL, BL Γ AL -> 11101110 Con la operación lógica δANDπ y δXORπ sucede exactamente lo mismo y como ya se supone conoces sus tablas de verdad, no lo voy a volver a explicar. La última operación lógica sí necesita un poco más de explicación ya que es un poco diferente en su forma: la instrucción 'δNOTπ'. 'δNOTπ' se diferencia de las demás porque sólo necesita un operando y lo que hace es invertir todos los bits del registro o posición de memoria (los unos se hacen ceros y los ceros unos). Así de sencillas son las instrucciones lógicas, solo recuerda que estas instrucciones modifican el estado de los flags. φ Los registros Ya sé que ya hemos hablado antes de los registros, pero no esta todo dicho pues nos centramos en lo que necesitabamos de inmediato que eran los registros de uso general y un poco de los segmentos. Los segmentos de memoria son un invento que se sacaron de la manga para abaratar y simplificar la arquitectura de los primeros PCs. Aquella solución aún hoy nos da quebraderos de cabeza cuando programamos en modo real (en modo protegido las cosas cambian), pero no hay otra solución... :( Un segmento mide 64Kb (65535 bytes) ya que para direccionarlo usamos un registro de segmento y otro que hace de índice dentro del segmento y que tiene una longitud de 16 bits (2^16=64Kb). Un dato a tener en cuenta es la "granula- ridad" de los segmentos, es decir, el solapamiento que se establece entre ellos y que es de 16 bytes. Si cojemos el primer segmento, por ejemplo, el 0000h y "avanzamos" 16 bytes, los datos que apartir de ahí nos encontremos, seran exac- tamente iguales que los que haya a partir del segmento 0001h (esto puede expe- rimentarse con el δDEBUGπ: entra y escribe la orden 'd 0000:0010' y luego escribe 'd 0001:0000' y observarás que el resultado es exactamente el mismo debido a este solapamiento). Cuando veamos los modos de direccionamiento, veremos que registros pueden hacer de índice y de base. Registros de segmento hay varios: CS (code segment), DS (data segment), SS (stack segment) y ES (extra segment). Los 3 primeros tienen una utilidad clara y bien definida, mientras que el último se asemeja a un registro de uso general. El registro CS es el que apunta al segmento de código, es decir donde estan las instrucciones que se estan leyendo (volveremos cuando hablemos de IP). El registro DS, en teoria, deberia apuntar a un segmento de datos que se suele crear al inicio del programa, aunque puede apuntar a cualquier lugar distinto. El segmento de pila (SS) es un segmento o parte de él usado para guardar los datos que se "empujan" a la pila interna (de la que hablaremos en próximos números) y las direcciones de retorno de subrutinas. Para cargar en un registro de segmento una dirección distinta se usa la polivalente instrucción 'MOV', pero con algunas limitaciones. Con 'MOV' no puedes modificar el registro CS aunque sí puedes coger su contenido y no puedes cargar en los registros valores inmediatos. Veamos lo que se puede y no se pue- de: Γ MOV ES, [Variable] Ω ; Se puede (mover una posición de memoria). Γ MOV DS, 1000h Ω ; No se puede (valor inmediato). Γ MOV CS, BX Ω ; No se puede (modificar segmento de código). Γ MOV AX, CS Ω ; Se puede (leer valor del seg. de código). Γ SUB ES, DS Ω ; No se puede (usar cosas raras :). Los registros δAX, BX, CX y DXπ son los llamados "de uso general" y no hay que pensar que sus iniciales son porque los de Intel estaban ociosos y buscaron unas iniciales que quedaran bien y pensaron en el abecedario (A+B+C+D)*X. La 'X' viene de 'extended' ya que en las 2 partes en que se dividen (xL y xH) son las simples y las otras las extendidas (por cierto, lo de AL y AH es por 'High' y 'Low' y hace mención a la parte que ocupan en el registro extendido AX). Lo realmente interesante es el porqué de la A, B, C y D, pues es una "causuali- dad" muy curiosa que hace referencia al uso que en un principio se destinó para los registros de "uso general" (si tenian un uso especifico, ¿porqué se les llamó "de uso general"?). La 'δAπ' es la inicial de 'Acumulator', pues es donde se acumulan los valores de algunas instrucciones aritméticas (p.e. la multiplicación). La 'δBπ' viene de 'Base' ya que es el registro de uso general que se puede usar como base en algunos modos de direccionamiento (luego). La 'δCπ' es la inicial de 'Counter' pues se usa como contador en instrucciones como 'LOOP' (comentada al principio). Por último, la 'δDπ' es de 'Data' ya que será el único registro que nos quedará libre para recibir los datos :) Cuando hablemos de los modos de direccionamiento le sacarás más partido a los 2 registros que vienen ahora. Se trata de δSIπ (source index) y δDIπ (destina- tion index) que en teoria tienen asignado el papel de registro de índice orígen y destino, aunque en la práctica se pueden usar como se quiera. Los índices, como se ha comentado antes, sirven para "direccionar" posiciones de memoria dentro de los segmentos y hasta que no explique los modos de direccionamiento no te sirven para nada :). Los registros δIPπ (index pointer) y δSPπ (stack pointer) son tambien 2 registros índice como los anteriores, pero con una funcionalidad un tanto especial que provoca que raramente se usen. El δIPπ apunta dentro del segmento de código (CS), a la instrucción que en cada momento se está ejecutando, mientras que el δSPπ apunta dentro del segmento de pila (SS) a la cima de dicha pila. El contenido de IP no se puede modificar salvo por las instrucciones de salto, su comportamiento es similar al de CS. El último registro que nos queda es BP (base pointer) que como su nombre indica hace de puntero base dentro de un segmento. Normalmente se asocia con la pila y sin falta alguna de razón, ya que si no especificas a que registro de segmento te refieres, usando BP se supone que es a SS (stack segment). φ Modos de direccionamiento Lo de los modos de direccionamiento puede resultar bastante pesado de aprender si te lo dan a "palo seco", sin ejemplos, sin una sóla instrucción. Los nombres que adquieren los modos de direccionamiento son lo de menos, ya los aprenderás con el tiempo por el uso, lo importante es qué se consigue con cada uno de ellos. Voy a poner ejemplos de cosas que se pueden hacer y con el nombre que recibe cada uno en el entorno PC. Γ MOV AX, 1054h Ω<- Direccionamiento inmediato Γ MOV AX, [1000h] Ω<- Dir. absoluto Γ MOV CX, [BX] Ω<- Dir. indirecto con base (BX,BP) Γ MOV ES:[BP+5000h], BL Γ MOV AL, [BX+0100h] Γ MOV [DI+10], BL Ω<- Dir. indirecto con índice (DI,SI) Γ MOV BH, [SI+10] Γ MOV AL, [BX+SI] Ω<- Dir. indirecto con base (BX,BP) e índice (SI,DI) Γ MOV CS:[BX+DI+50], BL Γ MOV [BP+SI+100], BH Γ MOV WORD PTR [BP+DI], 0010h Como habrás podido apreciar, son sólo 5 los modos de direccionamiento que existen en el modo real de un procesador Intel 80x86 y no demasiado difíciles. Antes de comentarlos, observa tres pequeños detalles en las intrucciones anteriores. El primero, son esos registros de segmento (en concreto ES y CS) que aparecen seguidos de ':' antes de algunos direccionamientos. La función de estos es indicar a la instrucción que lo que ha de leer, no lo haga del segmento predeterminado sino del que se le indica antes de los ':'. Por ejemplo, con SI siempre se busca por defecto en la zona de memoria apuntada por DS y si queremos que lea de CS, lo que tenemos que hacer es anteponer 'CS:'. El segundo detalle a observar, es en la última instrucción ese WORD PTR tan raro que no habia salido nunca hasta ahora. Se usa para indicar al ensamblador el tamaño de la instrucción ya que si no lo pusieramos no sabria si 0010h se considera un BYTE o una WORD (cabe tanto en un 8 bits como en 16). Con 'WORD PTR' le indicamos que el operando destino es una WORD y lo trata como debe. Si hubiesemos especificado 'BYTE PTR', seria tratado como un BYTE. El tercer detalle y más importante, son los operandos que aparecen entre corchetes. Cuando algo esta encerrado entre corchetes, inmediatamente hemos de pensar que se trata de una posición de memoria, algo que se encuentra almacena- do en RAM y que hemos de traer. Por ejemplo, ¿Que diferencia hay entre "MOV BX, 100h", "MOV BX,[100h]" y "MOV [BX],100h"? La primera, copiará en el registro BX el valor 100h; la segunda, se irá a memoria (concretamente a la posición DS:0100h) y leerá una WORD que copiará en BX; la tercera, cogerá el valor 100h y lo guardará en la posición de memoria DS:BX (donde apunte el contenido de BX). Volvamos a los modos de direccionamiento. El direccionamiento inmediato es muy usado sobre todo para inicializar variables y registros. Hay gente que no lo considera un modo de direccionamiento en sí mismo ya que se puede combi- nar con otros como en la última instrucción o, por ejemplo: Γ MOV WORD PTR DS:[BX+DI+1500h], 1000h Γ MOV BYTE PTR [BX], 10h El direccionamiento absoluto es usado muy frecuentemente en un programa ya que todas las variables que no residan en registros se deben guardar en memoria y para acceder a ellas se suele usar este modo pues es el más sencillo. Por ejemplo, imagina un programa que te pida una letra por teclado y la almacene en un determinado lugar de la memoria: Γ ···· Γ Letra DB 0 Γ ···· Γ PideLetra: Ω; Función que guarda una tecla en Letra. Γ XOR AH, AH Γ INT 16h Ω ; Pedir una tecla a la INT del teclado. Γ MOV [Letra], AL Ω ; Dir. absoluto para guardar la tecla leida. Γ RET Γ ···· Γ Main: Γ MOV AX, CS Γ MOV DS, AX Ω ; Por defecto DS que apunte a los datos. Γ CALL PideLetra Ω ; Llamamos a la función. Γ ···· Los direccionamientos relativos a base o registros índice, se suelen usar para fijar una dirección "base" en las lecturas y para recorrer arrays y estructuras similares. Por ejemplo, tenemos una matriz de 500 elementos y queremos hacer una función que devuelva en AX un 1 si no hay ningún elemento con el valor 0 y un 0 en AX si hay elementos a 0: Γ ···· Γ Matriz DB 32, 43, 243, 54, ··· Γ ···· Γ Check: Γ MOV AX, 1 Ω ; Por defecto Γ MOV BP, Offset MatrizΩ ; Dirección base -> el inicio de la matriz. Γ XOR SI, SI Ω ; SI <- 0, indice en la matriz a 0. Γ Bucle: Γ CMP DS:[BP+SI], 0 Ω ; El elemento de [BP+SI] esta a 0? Γ JE UnoCon0 Γ INC SI Ω ; Siguiente elemento. Γ CMP SI, 100 Ω ; El último elemento? Γ JB Bucle Γ JMP Regresar Ω ; Saltar para regresar. Γ UnoCon0: Γ XOR AX, AX Ω ; Marcar como que hay elementos a 0. Γ Regresar: Γ RET Ω ; Volver al llamador. Γ ···· Γ Main: Γ ···· Γ CALL Check Γ OR AX, AX Ω ; Lo mismo que 'CMP AX,0' pero más corto. Γ JE HayCeros Γ ···· Este pedazo de código que aparentemente no tiene ninguna utilidad real, en determinados ámbitos de la programación puede usarse muchas veces (cosas similares) como en la programación gràfica para ver si en la pantalla hay algún pixel con el color 0. Recordatorio: en la línea donde se inicializa el registro base, se ha usado la palabra clave 'offset XX', significa "el despla- zamiento dentro del segmento correspondiente hasta la variable XX". En el número de hoy hemos dado un somero repaso a todos los conceptos e ideas que ya habiamos expuesto y que faltaba consolidar por lo que hemos ido tirando de teoria todo el rato. Para el próximo número os prometo que la cosa será mu- cho más práctica y con varios programas para ensamblar. Veremos todo lo rela- cionado con la pila interna, algunas instruciones nuevas y algún ejemplo rela- cionado con los gráficos o la VGA, ya veremos... ∞ Navi/PhyMosys